package weeny.pre.face.operation;

import static weeny.pre.io.parser.EnumParser.SHAPE_PARSER;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JTextField;

import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.column.Column;
import prefuse.data.expression.Expression;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ObjectParser;
import prefuse.data.tuple.TupleSet;
import prefuse.visual.VisualItem;
import weeny.face.LabeledSpinner;
import weeny.pre.face.ColorTextField;
import weeny.pre.face.EnumComboBox;
import weeny.pre.face.ExpressionBox;
import weeny.pre.io.parser.EnumParser;
import weeny.pre.util.IntColor;
import weeny.util.io.ModUtil;
import weeny.util.logs.Debug;

/**
 * Change a field value of all the filtered tuples.
 *  
 * 1) If the filter is null, which means all items are selected, a popup window
 *    will show up to confirm (or you can hold alt key to skip the comfirm).
 * 2) Choosing the title item can force to recollect all possible column names
 *    throughout the visualization.
 * 3) Holding alt key when choosing a column name can switch to ExpressionBox,
 *    so you can input expression to set different values for different typle,
 *    otherwise, you just set all filtered tuples with the same value.
 * 
 * Please notice: if they are all set with the same value, then I just call:
 * tuple.setString(activeField, (String) value); where activeField is the column
 * name we get from the column combobox, value is the string value we get from
 * the user input. Usually columns know how to parse the string value to get
 * what they want, because they use {@link DataParser} to parse the string. But
 * there is no guarantee.
 * 
 * @author weeny
 *
 */
public class ColumnValueOperation extends AbstractOperation{
	
	/**
	 * Used when we find different tables have the same name you have chosen but
	 * different types of contents.
	 */
	protected static final JTextField BAD_CHOICE_MSG = new JTextField(); 
	static {
		BAD_CHOICE_MSG.setText("Mixed or missing column");
		BAD_CHOICE_MSG.setEnabled(false);
	}

	/**
	 * Some hand picked most useful column names, which will be shown at the
	 * beginning of the list.
	 */
	protected static final List<String> USUAL_FIELD = new ArrayList<String>();
	static {
		USUAL_FIELD.add(VisualItem.FILLCOLOR);
		USUAL_FIELD.add(VisualItem.STROKECOLOR);
		USUAL_FIELD.add(VisualItem.TEXTCOLOR);
		USUAL_FIELD.add(VisualItem.SIZE);
		USUAL_FIELD.add(VisualItem.SHAPE);
		USUAL_FIELD.add(VisualItem.LABEL);
		USUAL_FIELD.add(VisualItem.VISIBLE);
		USUAL_FIELD.add(VisualItem.X);
		USUAL_FIELD.add(VisualItem.Y);
		Collections.sort(USUAL_FIELD);
	}
	
	//several choices for the detail component
	protected JTextField   inputField = new JTextField();
	protected JCheckBox      checkBox = new JCheckBox();
	protected EnumComboBox  enumCombo = new EnumComboBox();
	protected LabeledSpinner  spinner = new LabeledSpinner("");
	protected ExpressionBox    expBox = null;
	protected ColorTextField colorFld = new ColorTextField();
	/**
	 * listener monitoring all the possible detail components, although only one
	 * of them is active at any time
	 */
	protected ActionListener listener = new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			Object value = null;
			if (e.getSource() instanceof LabeledSpinner) {
				value = ((LabeledSpinner)e.getSource()).getValue();
			}
			else if (e.getSource() instanceof ExpressionBox) {
				value = ((ExpressionBox)e.getSource()).getExpr();
			}
			else if (e.getSource() instanceof JTextField) {
				value = ((JTextField)e.getSource()).getText();
			}
			else if (e.getSource() instanceof JCheckBox) {
				value = ((JCheckBox)e.getSource()).isSelected();
			}
			else if (e.getSource() instanceof JComboBox) {
				value = ((JComboBox)e.getSource()).getSelectedItem().toString();
			}
			else if (e.getSource() instanceof ColorTextField) {
				value = ((ColorTextField)e.getSource()).getColor();
			}

			int updateCnt = 0; 
			int sameCnt = 0;
			int errorCnt = 0; 
			int totalCnt = 0;
			//now we go through every tuple to set each value
			boolean override = ModUtil.isAltDown(e.getModifiers());
			Iterator<Tuple> it = operationBox.filteredItems(override);
			while (it.hasNext()){
				Tuple tuple = it.next();
				totalCnt ++;
				Object old = tuple.get(activeField);
				if (value instanceof Expression) {
					try {
						tuple.set(activeField, ((Expression) value).get(tuple));
						if (old.equals(tuple.get(activeField))) {
							sameCnt ++;
						}
						else {
							updateCnt ++;
						}
					} catch (Exception e2) {
						errorCnt ++;
					}
				}
				else if (value instanceof String){
					try {
						tuple.setString(activeField, (String) value);
						if (old.equals(tuple.get(activeField))) {
							sameCnt ++;
						}
						else {
							updateCnt ++;
						}
					} catch (Exception e2) {
						errorCnt ++;
					}
				}
				else {
					try {
						tuple.set(activeField, value);
						if (old.equals(tuple.get(activeField))) {
							sameCnt ++;
						}
						else {
							updateCnt ++;
						}
					} catch (Exception e2) {
						errorCnt ++;
					}
				}
			}
			
			//show the operation statistics
			String msg ="There are "+totalCnt+" in total: "+updateCnt+
					" updated, "+sameCnt+" unchanged, "+errorCnt+" errors";
			Debug.info(msg);
			operationBox.updateInfo(msg);
			operationBox.getVisualization().repaint();
		}
	};
	/**
	 * Reset the itemList from every visual table from the visualization.
	 */
	protected void resetAllColumns(){
		itemList.clear();
		Set<String> groups = operationBox.getVisualization().getGroups();
		Set<String> fields = new HashSet<String>();
		for (String gr : groups) {
			TupleSet ts = operationBox.getVisualization().getVisualGroup(gr);
			if (ts == null || !(ts instanceof Table)) {
				continue;
			}
			Table table = (Table) ts;
			for(int i = 0; i<table.getColumnCount(); i++){
				Column column = table.getColumn(i);
				if (column.getParser().getClass().equals(ObjectParser.class)) {
					//cannot parse the string, so no need to list it
					continue;
				}
				fields.add(table.getColumnName(i));
			}
		}
		List<String> temp = new ArrayList<String>(fields);
		Collections.sort(temp);
		temp.removeAll(USUAL_FIELD);
		itemList.addAll(USUAL_FIELD);
		itemList.add(SEPARATOR);
		itemList.addAll(temp);
	}
	public ColumnValueOperation(OperationBox operationBox) {
		super(operationBox);
		title = "Value Action";
		inputField.addActionListener(listener);
		checkBox.addActionListener(listener);
		enumCombo.addActionListener(listener);
		spinner.addActionListener(listener);
		expBox = new ExpressionBox(operationBox.getVisualization(), "");
		expBox.addActionListener(listener);
		expBox.resizeCombo(new Dimension(60, 30));
		colorFld.addActionListener(listener);
		colorFld.addKeyListener(new KeyListener() {
			public void keyTyped(KeyEvent e) {
				System.out.println(e.getKeyChar()+":"+e.getModifiers());
				System.out.println(e.getID()+":");
			}
			
			public void keyReleased(KeyEvent e) {
			}
			
			public void keyPressed(KeyEvent arg0) {
			}
		});
		resetAllColumns();
	}
	
	String activeField = null;
	public void itemInvoked(String item, boolean isAltDown) {
		Debug.info("Item changed to: "+title+" -> "+item);
		if (item.equals(title)) {
			//if the title is chosen, we refresh the item list
			resetAllColumns();
			operationBox.updateAllOperations();
			operationBox.updateDetail(null);
			operationBox.updateInfo("All column names collected");
			Debug.info("All column names collected");
			return;
		}
		else if (item.equals(SEPARATOR)){
			return;
		}
		else if (itemList.contains(item)){
			activeField = item;
			if (isAltDown) {
				operationBox.updateDetail(expBox);
			}
			else {
				operationBox.updateDetail(chooseComponent(item));
			}
		}
	}
	protected Component chooseComponent(String item){
		Class type = columnType(operationBox.getFilter(), item);
		DataParser dp = dataParser(operationBox.getFilter(), item);
		if (item.equals(VisualItem.SHAPE)) {
			if (dp == null || dp != SHAPE_PARSER) {
				//make sure that _shape field has the enumParser
				Set<Table> tables = itemTables(operationBox.getFilter());
				for (Table table : tables) {
					Column column = table.getColumn(VisualItem.SHAPE);
					column.setParser(SHAPE_PARSER);
				}
				dp = SHAPE_PARSER;
			}
		}
		if (type == null){
			//cannot find a consistent type for all items in that column
			return BAD_CHOICE_MSG;
		}
		if (dp != null && dp instanceof EnumParser) {
			//if the type is enum
			enumCombo.removeActionListener(listener);
			enumCombo.resetEnum((EnumParser)dp);
			enumCombo.addActionListener(listener);
			return enumCombo;
		}
		if (type == boolean.class) {
			//if the type is boolean
			checkBox.setText(item);
			return checkBox;
		}
		if (type == double.class) {
			spinner.setSpinnerModel(0.0, 0.1);
			return spinner;
		}
		if (type == int.class) {
			if (ColorTextField.COLOR_FLD_PATTERN.matcher(item).matches()) {
				colorFld.setColor(IntColor.GREEN);
				return colorFld;
			}
			else {
				spinner.setSpinnerModel(0, 1);
				return spinner;
			}
		}
		//the detault choice
		inputField.setText("");
		return inputField;
	}
}